package BNS.plumbing;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

import BNS.graphs.Node;


/** This class interacts with the Client once it has been registered.
	It carries out the mechanics of starting and stopping experiments (aka games).
	Its role is to communicate with the Client, verify its identity, and monitor its
	responsiveness (to detect connection drops). 
	It ALSO is involved in the routine updates of game state, and being involved in
	this second activity is very useful in performing the first.
	These 2 mandates satisfy different requirements coming from different
	parts of the BNS system written by different people at different times.
	
	<p>The internal StatusCarrier class carries the two channels simultaneously.
	Since it has no knowledge of the particular game being played, the messages
	from the client to the game are conveyed simply as Serializable objects.
	It is up to the game to reconstitute them as their natural object types
	and to interpret them.
	</p>
	*/
public class ClientLiaison extends UnicastRemoteObject 
							implements ClientLiaisonInterface {
/**
   * 
   */
  private static final long serialVersionUID = 1L;
  //	public final SessionHost host;
	public final String playerName;
	public final int clientID;
	private int nodeIdx;
	public Node eigenNode;
	protected BNSPersona persona;
	protected BNSGameController game;
	private String status;
	private Serializable envelope;
	boolean empty, tardy, dismissFlag, newPersona;
	long lastUpdate;//, lastPayDate;
	
	/** creates a ClientLiaison object that will connect to a Client applet.
		@param n	the name of the player using the applet
		@param cID	a number given that will be treated as an anonymous representative of the name
//		@param h	the SessionHost that is coordinating the whole game session.
		*/
	public ClientLiaison(String n, int cID) throws RemoteException {
		playerName=n; clientID=cID; status="WAIT"; persona=null;
//		host= h;
		empty=false; tardy=false; dismissFlag=false; newPersona=false;
		lastUpdate=System.currentTimeMillis();
		}
	
	/** tells the liaison object what game will be played next.
		@param gc	the game being set up
		@param node	the node that will be operated by this Client.
		*/
	public synchronized void adoptGame(BNSGameController gc, Node node) {
		nodeIdx= (node==null)? clientID : node.getIndex();
		game= gc;	// synchrony connects this line to next
		persona= game.getPersona(this,node);
		newPersona= true;
		eigenNode= persona.getEigenNode();
		sendMessage("loading "+ game.getTitle());
//		lastPayDate= game.getElapsedTime();
		//System.err.println("adoptGame "+ node);
		}
	
	/** tells the liaison object that someone with the same name has re-registered, 
		perhaps by hitting the reload button on his browser. S/he should be sent
		the same Persona that s/he was supposed to be playing.
		*/
	public synchronized void reAdoptGame() { newPersona= true; }
	
	/** called (by the remote Client!) to obtain all the information about
		the state of the game relevant to that player's view of the game.
		TODO: As an added security measure, these calls should be monitored to ensure that 
		they (and calls to alter) always come from the same IP address.
		@return a game.Envelope filled with assorted game data.
		*/
	public synchronized Serializable passUpdate() throws RemoteException { 
		//System.err.println("calling update for client "+ clientID);
		// TODO: test clientID
		lastUpdate= System.currentTimeMillis();
		if (dismissFlag) return null;	// all games are over; tell Client to resign
		if (newPersona) {
			System.err.println("sending persona "+ persona.getTitle());
			BNSPersona pers= persona;
			newPersona= false;
			return pers;
			}
		envelope= game.getUpdate(nodeIdx);
		if (envelope instanceof ClientLiaison.StatusCarrier) {
			((ClientLiaison.StatusCarrier)envelope).status= status;	// send message just once...
			status= null;	// clears out a status message that has been sent
			}
		return envelope;
		}
	
	/** called by the SessionHost to indicate to the Client that the session is over.
		*/
	public synchronized void dismiss() {
		dismissFlag= true;	// triggers resignation
		}
	
	/** called by the SessionHost to send a status message to the client.
		*/
	public synchronized void sendMessage(String msg) {
		status= msg;	// will be sent once by passUpdate, and then cleared
		}
	
	/** called by the remote Client to tell the server that the client has retired from play.
		Resources allocated to it should be recovered, unless there is reason
		to believe this is an inadvertent act. 
		* /
	public synchronized boolean resign() throws RemoteException {
		System.err.print(" client "+clientID+" asking to resign, ");
		// TODO: test clientID
		if (game!=null) {
			//notify the Monitor & human supervisor that it lost a client
			//game= null; ???  No... let closeGame clean this up.
			}
		empty= true;
		host.resign(this);
		// Clean up the exported remote object.
		// When true, second parameter forces even pending calls to fail.
		try { UnicastRemoteObject.unexportObject((java.rmi.Remote)this,false); }
		catch(NoSuchObjectException e) {}
		return true;
		}
	
	/** passes client requests for state changes through to the appropriate game,
		along with identification of which client is making the request. 
		*/
	public synchronized boolean alter(Serializable changeObj) throws RemoteException { 
		return game.alter((ClientLiaison)this, changeObj); }
	
	/** A general-purpose mechanism to convey a status message to the client.
		Some parts of the system (e.g. SessionHost) have no knowledge of the game 
		or its Envelope. This class provides a convenient way of piggybacking 
		a message onto the Envelope so that it gets sent by passUpdate().
		Every BNSGame's Envelope class should extend this class.
		*/
	public static class StatusCarrier implements java.io.Serializable {
		/**
     * 
     */
    private static final long serialVersionUID = 1L;
    public String status;
		public StatusCarrier(String msg) { status=msg; }
		}
	
	}
